package vv.lines.core;

import vv.lines.core.context.ContextHolder;
import vv.lines.core.context.IContext;

import java.util.*;

import static vv.lines.utils.LinesConstants.SOURCE;
import static vv.lines.utils.LinesConstants.TARGET;
import static vv.lines.utils.LinesUtils.equalsBall;
import static vv.lines.core.Orientation.*;

/**
 *
 * @author vpotapenko
 */
public class CellController extends ContextHolder {

    private CellHolder cellHolder;

    public CellController(Field field) {
        super(field.getContext());
        final IContext context = getContext();
        cellHolder = new CellHolder(field, context.getColumnCount(), context.getRowCount());
    }

    public Collection<Cell> getColorArea(Collection<Cell> lastActive) {
        Collection<Cell> area = new HashSet<Cell>();
        for (Cell cell : lastActive) {
            if (cell != null) {
                checkColorLine(cell, BDIAGONAL, area);
                checkColorLine(cell, FDIAGONAL, area);
                checkColorLine(cell, HORIZONTAL, area);
                checkColorLine(cell, VERTICAL, area);
            }
        }

        return area;
    }

    public void clearArea(Collection<Cell> area) {
        for (Cell cell : area) {
            if (cell != null) cell.setBall(null);
        }
    }

    public Collection<Cell> getRandomCells() {
        int number = getContext().getGenerateCount();
        HashSet<Cell> result = new HashSet<Cell>();

        Random rand = new Random();
        List<Cell> freeCells = cellHolder.getFreeCells();
        while (number > 0 && freeCells.size() > 0) {
            int index = rand.nextInt(freeCells.size());
            Cell cell = freeCells.get(index);

            result.add(cell);
            freeCells.remove(index);

            number--;
        }

        return result;
    }

    public void clearAll() {
        cellHolder.clearAll();
    }

    public List<Cell> getPath(Cell source, Cell target) {
        generateValuesOnCells();

        source.setValue(SOURCE);
        target.setValue(TARGET);

        doWaves(target);

        if (!hasPath(target)) return Collections.emptyList();

        List<Cell> path = new ArrayList<Cell>();
        path.add(target);

        gatherPath(target, path);
        return path;
    }

    public void updateCellsBounds(int fieldWidth, int fieldHeight) {
        cellHolder.updateCellsBounds(fieldWidth, fieldHeight);
    }

    public boolean hasFreeCells() {
        return cellHolder.hasFreeCells();
    }

    private void checkColorLine(Cell cell, Orientation orientation, Collection<Cell> area) {
        Collection<Cell> line = new HashSet<Cell>();

        line.add(cell);
        checkColorLine(cell, orientation, line, true);
        checkColorLine(cell, orientation, line, false);

        if (line.size() >= getContext().getClearableSize()) area.addAll(line);
    }

    private void checkColorLine(Cell cell, Orientation orientation, Collection<Cell> line,
                                boolean ascend) {
        Cell peer = cellHolder.getPeerCell(cell.getColumn(), cell.getRow(), orientation, ascend);
        while (equalsBall(cell, peer)) {
            line.add(peer);
            peer = cellHolder.getPeerCell(peer.getColumn(), peer.getRow(), orientation, ascend);
        }
    }

    private void doWaves(Cell target) {
        boolean doIteration = true;
        while (doIteration) {
            int count = 0;
            for (int col = 0; col < cellHolder.getColumnCount(); col++) {
                for (int row = 0; row < cellHolder.getRowCount(); row++) {
                    count += fillValuesOnCell(cellHolder.getCell(col, row));
                }
            }
            doIteration = (count > 0) && !hasPath(target);
        }
    }

    private int fillValuesOnCell(Cell cell) {
        if (cell == null) return 0;

        int value = getNextValue(cell.getValue());
        if (value == 0) return 0;

        int count = 0;
        Collection<Cell> peers = cell.getPeers();
        for (Cell c : peers) {
            if (c.getValue() == 0) {
                c.setValue(value);
                count++;
            }
        }
        return count;
    }

    private int getNextValue(int value) {
        if (value <= 0) {
            return value == SOURCE ? 1 : 0;
        } else {
            return value + 1;
        }
    }

    private void gatherPath(Cell currCell, List<Cell> path) {
        Collection<Cell> peers = currCell.getPeers();
        Cell foundCell = null;
        for (Cell cell : peers) {
            if (cell.getValue() == SOURCE) {
                foundCell = cell;
                path.add(0, foundCell);
                return;
            }

            int value = cell.getValue();
            if (value <= 0) continue;

            if (foundCell == null || foundCell.getValue() > value) {
                foundCell = cell;
            }
        }
        if (foundCell != null) {
            path.add(0, foundCell);
            gatherPath(foundCell, path);
        }
    }

    private void generateValuesOnCells() {
        for (int col = 0; col < cellHolder.getColumnCount(); col++) {
            for (int row = 0; row < cellHolder.getRowCount(); row++) {
                Cell cell = cellHolder.getCell(col, row);
                cell.generateInitValue();
            }
        }
    }

    private boolean hasPath(Cell target) {
        Collection<Cell> peers = target.getPeers();
        for (Cell cell : peers) {
            if ((cell.getValue() > 0) || cell.getValue() == SOURCE) return true;
        }
        return false;
    }
}
